/* proc.c
   By: Teresa Hume
   Date Created: March 19th 2011

   Modified By: Samuel Ferguson
   Date Modified: April 4 2011
*/

#include <proc.h>
#include <definitions.h>
#include <xxxio.h>

struct pcb
{
  int esp;
  int ss;
  int flags;
  int cs;
};

/* the current task struct that is running */
struct task_struct* current;

/* XXX Sam notes: This does not make sense. We already have a pointer to 
   the current task_struct and all its fields are accessible. */
/* global variable for current kernel stack current->esp0 */
void* current_kstack;
/* global variable for current process stack current->sp */
void* current_pstack;

struct task_queue tasks[NUM_QUEUES];

void printTask(struct task_struct* task)
{
  xprintf("printTask()----------------\n");
  xprintf("task address: %i\n", task);
  xprintf("task state: %i\n", task->state);
  xprintf("task pid: %i\n", task->pid);
  xprintf("task ppid: %i\n", task->ppid);
  xprintf("task code address: %x\n", task->mm->start_code);
  xprintf("task heap address: %x\n", task->mm->start_brk);
  xprintf("task brk  address: %x\n", task->mm->brk);
  xprintf("task stack addres: %x\n", task->mm->start_stack);
  xprintf("task esp address: %x\n", task->esp0);
  xprintf("task sp: %x\n", task->sp);
  xprintf("task next adddress: %x\n", task->next);
  xprintf("---------------------------\n");
}

void print_running_queue(){
  xprintf("print_running_queue()----------------\n");
  xprintf("curr_tasks: %i\n",tasks[0].curr_tasks);
  xprintf("head: %i\n",tasks[0].head);
  xprintf("tail: %i\n",tasks[0].tail);

}
/* tester funtion to print the current stack information */
void printCurrEsp()
{
  xprintf("current_kstack: %x\n", current_kstack);
  xprintf("current esp0: %x\n", current->esp0);
  xprintf("current sp: %x\n", current->sp);
  xprintf("current: %x\n", current);
}

/* method to retrieve the current task */
struct task_struct* getCurrentTask()
{
  /* printTask(current); */
  return current;
}

/* 
 * method to set the current task 
 * note: before setting a new process to run the
 * 	current process should be enqueued on the TASK_RUNNING
 * 	or TASK_STOPPED queue (depending on whether or not the
 * 	process has completed execution.
 */
void setCurrentTask(struct task_struct* new_task)
{
  if(NULL == new_task){
    xprintf("proc.c:%i new_task is NULL\n",__LINE__);
    return;
  }

  /* printTask(new_task); */
  current->esp0 = current_kstack;
  current = new_task;
  current_kstack = current->esp0;
  current_pstack = current->sp;
  /* xprintf("proc.c:%i the new esp is %i\nthe new task address is %i\n",  */
  /* 	  __LINE__,current_kstack, current); */
  return;
}

/* copying a task struct to a new task struct */
/* could be used to fork a process */
int copy_task(struct task_struct* new, struct task_struct* old)
{
  if(NULL == new || NULL == old){
    return -ERR_NULL;
  }
  /* initializing the new task_struct's values */
  *new = *old;
  new->next = old->next;
  old->next = new;
  /* printTask(new); */
  return SUCCESS;
}

/* initializing a task_struct with the given values */
int init_task(struct task_struct* task, int state,
	      pid_t pid, pid_t ppid, 
	      struct mm_struct* mm, void (*code_ptr)())
{
  /* xprintf("proc.c:%i, task address: %i\n", __LINE__, task); */
  /* xprintf("proc.c:%i, pid: %i\n",__LINE__, pid); */
  /* xprintf("proc.c:%i, mm address: %i\n",__LINE__, mm); */
  if(pid != 0 && NULL == mm){
    xprintf("proc.c:%i, failed to initialize task\n", __LINE__);
    return -ERR_NULL;
  }

  /* setting the fields of the new task_struct */
  /* xprintf("pid: %i\n", pid); */
  task->state = state;
  task->pid = pid;
  task->ppid = ppid;
  task->esp0 = mm->start_stack;

  task->mm = mm;
  task->mm->start_code = code_ptr;
  
  unsigned int address = 0;
  void* temp_esp = task->esp0;

  xprintf("proc.c:%i process %i esp0 before push: %x\n", __LINE__,  
  	  pid, task->esp0); 

  /* struct fake_PCB{ */
  /*   uint32_t flags;  */
  /*   uint32_t cs; */
  /*   uint32_t code; */
  /*   uint32_t save_function; */
  /*   uint32_t old_eax; */
  /*   uint32_t second_save_function; */
  /*   uint32_t ecx; */
  /*   uint32_t eax; */
  /*   uint32_t ebx; */
  /*   uint32_t no_idea; */
  /*   uint32_t ebp; */
  /*   uint32_t esi; */
  /*   uint32_t edi; */
  /* }fakepcb = {0x216, 0x00000008, 0x9f52, 0xa19f, 0x0, 0xa19f, 0xb8320, 0x0, 0x1fff8f, */
  /* 	      0x1fff3b, 0x1fff67, 0xfee090fe, 0x1}; */
  /* task->esp0 -= sizeof(struct fake_PCB); */
  /* for(address=0;  */
  /*     address <= sizeof(struct fake_PCB); address += sizeof(char)) */
  /*   *((char*)(task->esp0 + address)) = 0; */
    
  /* *((struct fake_PCB*)(task->esp0)) = fakepcb; */

  /* setting up a fake PCB for the new process, so we can switch into it */
  asm
    (
     /* saving the actual esp */
     "movl %%esp, %[esp_real];"
     /* switching the esp to the new process's esp0 */
     "movl %[esp_next], %%esp;"
     /* pushing the flags */
     "pushf;"
     /* pusging the current code segment */
     "push %%cs;"
     /* pushing the entry point into the process */
     "push %[code_pointer];"
     "push $0x0;" /* skipped */
     "push $0x0;" /* pop eax */
     "pusha;"
     /* setting the new process's esp to the top of the stack */
     "movl %%esp, %[esp_next];"
     /* "jmp .;" */
     /* resetting the esp to the original stack address */
     "movl %[esp_real], %%esp;"
     : /* not outgoing regs */
     : [code_pointer] "r" (mm->start_code),
       [esp_real] "r" (address),
       [esp_next] "r" (temp_esp)
     );
  task->esp0 = temp_esp;
  task->sp = temp_esp;

  xprintf("proc.c:%i, process %i esp0 after push: %x\n", __LINE__,
  	  pid, task->esp0);

  /* xprintf("proc.c:%i, esp real %i\n",__LINE__, address); */

  /* printTask(task); */

  return SUCCESS;
}

/* initializing the task_queues */
int init_task_queues()
{
  int i = 0;
  for(i = 0 ; i < NUM_QUEUES; i++){
    tasks[i].curr_tasks = 0;
    /* head and tail are out of bounds if the queue is empty */
    tasks[i].head = -1;
    tasks[i].tail = -1;
  }
  
  return SUCCESS;
}

/* adding a task_struct to the task_queue */
int enqueue_task(struct task_struct* task)
{
  int prev_proc = 0;
  
  if(NULL == task || task->state >= NUM_QUEUES || task->state < 0){
    xprintf("proc.c:%i, failed to enqueue: invalid data\n",__LINE__);
    return -ERR_INVALID;
  }
  else if(tasks[task->state].curr_tasks == MAX_TASKS){
    xprintf("proc.c:%i, failed to enqueue: full queue\n",__LINE__);
    return -ERR_FULL;
  }
  /* if the queue is empty, start it off */
  if(tasks[task->state].curr_tasks == 0){
    /*add task to head of the array*/
    tasks[task->state].task_queue[0].data = task;
    /* increment the curr_tasks and tail position */
    tasks[task->state].curr_tasks += 1;
    tasks[task->state].head += 1;
    tasks[task->state].tail += 1;
    /*printTask(&tasks[task->state].task_queue[tasks[task->state].head]);*/
    return SUCCESS;
  }
  /* add item to the tail (check wraparound first) */
  prev_proc = tasks[task->state].tail;
  tasks[task->state].tail += 1;
  if(tasks[task->state].tail == MAX_TASKS){
    tasks[task->state].tail = 0;
  }
  
  /* once the tail is set copy the task to the new tail position */
  tasks[task->state].task_queue[tasks[task->state].tail].data = task;
  
  /* setting the next pointers for the previous task and the new task */
  /* doubly linked list */
  tasks[task->state].task_queue[prev_proc].next = 
    tasks[task->state].task_queue[tasks[task->state].tail].data;

  tasks[task->state].task_queue[tasks[task->state].tail].next =
    tasks[task->state].task_queue[tasks[task->state].head].data;

  /* incrememnt the number of tasks in the queue */
  tasks[task->state].curr_tasks += 1;

  return SUCCESS;
}

/* function to dequeue a task from the queue with queue_id */
int dequeue_task(struct task_struct** task, int queue_id)
{
  /* if the data is invalid return an error */
  if(NULL == task || queue_id >= NUM_QUEUES || queue_id < 0){
    xprintf("ERROR in dequeue_task proc.c:%i \n",__LINE__);
    return -ERR_INVALID;
  }
  
  if(0 == tasks[queue_id].curr_tasks){
    xprintf("ERROR in dequeue_task proc.c:%i \n",__LINE__);
    return -ERR_INVALID;
  }
  
  *task = tasks[queue_id].task_queue[tasks[queue_id].head].data;
  
  /* setting the head of the queue to the next task */
  tasks[queue_id].head += 1;
  /* in case of overflow wrap back around to 0 */
  if(tasks[queue_id].head == MAX_TASKS){
    tasks[queue_id].head = 0;
  }
  
  /* decrement the number of tasks in the queue */
  tasks[queue_id].curr_tasks -= 1;
  
  /* if the queue is now empty, set the head and tail to
   * the default values (-1) */
  if(tasks[queue_id].curr_tasks == 0){
    tasks[queue_id].head = -1;
    tasks[queue_id].tail = -1;
  }
  
  return SUCCESS;
}
